package com.qijian.maindata.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qijian.common.exception.ServiceException;
import com.qijian.common.utils.ObjectUtils;
import com.qijian.maindata.domain.BomDetail;
import com.qijian.maindata.domain.LineProcessTime;
import com.qijian.maindata.domain.query.BomDetailInfoQuery;
import com.qijian.maindata.domain.query.BomDetailQuery;
import com.qijian.maindata.domain.vo.*;
import com.qijian.maindata.mapper.BomDetailMapper;
import com.qijian.maindata.service.IBomDetailService;
import com.qijian.tool.domain.Storage;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * BOM明细Service业务层处理
 *
 * @author qijian
 * @date 2021-09-01
 */
@Service
@RequiredArgsConstructor
public class BomDetailServiceImpl extends ServiceImpl<BomDetailMapper, BomDetail>
        implements IBomDetailService {

    private final BomDetailMapper bomDetailMapper;

    /**
     * 艺管理-员工收益 钻取Bom和数控件详情
     * @param query
     * @return
     */
    @Override
    public List<BomDetailVo> selectVo(BomDetailQuery query) {

        // 查看数控件
        if(ObjectUtils.isNotEmpty(query.getItemName())){
            return baseMapper.listNcDetail(query);
        }

        // 查看Bom明细
        return baseMapper.listBomDetail(query);
    }

    @Override
    public List<BomDetailVo> selectVo(QueryWrapper<BomDetailQuery> queryWrapper, Integer type) {
        return bomDetailMapper.selectVo(queryWrapper, type);
    }

    @Override
    public List<BomDetailVo> selectBomAndLineVo(QueryWrapper<BomDetailQuery> queryWrapper) {
        return bomDetailMapper.selectBomAndLineVo(queryWrapper);
    }

    @Override
    public List<BomDetailAllVo> selectAllVo(QueryWrapper<BomDetailQuery> queryWrapper) {
        queryWrapper.groupBy("main_bom_detail.detail_id , main_bom_detail.detail_code" +
                ", main_bom_detail.line_id , main_bom_detail.sub_count" +
                ", main_bom_detail.line_code , main_bom_detail.level" +
                ", main_bom_detail.pid , main_bom_detail.bom_id" +
                ", main_bom_detail.item_code , main_bom_detail.quantity" +
                ", main_bom_detail.sort , main_bom_detail.item_id" +
                ", main_bom_detail.total_quantity, main_bom_detail.update_time" +
                ", main_bom_detail.producer_id , main_bom_detail.producer_name" +
                ", main_bom_detail.approval_by , main_bom_detail.approval_time" +
                ", main_bom_detail.approval_status , main_item.item_code" +
                ", main_item.item_name , main_item.item_classify" +
                ", main_item.item_spec , main_item.item_units" +
                ", main_item.ora_combo , main_item.part_no , main_item.drawing_no , main_item.type_name" +
                ", main_item.item_weight");
        return bomDetailMapper.selectAllVo(queryWrapper);
    }

    @Override
    @Deprecated
    public Page<BomDetailVo> pageVo(Page<BomDetail> page, QueryWrapper<BomDetailQuery> queryWrapper) {
        return bomDetailMapper.selectPageVo(page, queryWrapper);
    }

    @Override
    public List<BomDetailVo> listVo(QueryWrapper<BomDetailQuery> queryWrapper) {
        return bomDetailMapper.selectPageVo(Page.of(0, -1), queryWrapper).getRecords();
    }

    @Override
    public List<BomDetailVo> selectNc(Long bomId) {
        return bomDetailMapper.selectNc(bomId);
    }

    @Override
    public Page<BomDetailInfoVo> selectBomInfo(Page<BomDetailInfoVo> page, QueryWrapper<BomDetailInfoQuery> queryWrapper) {
        return bomDetailMapper.selectBomInfo(page, queryWrapper);
    }

    @Override
    public List<Storage> getFiles(Long bomId) {
        return bomDetailMapper.getFiles(bomId);
    }

    @Override
    public void calProcessTime(List<BomDetailVo> details, Date time, Map<String, LocalDateTime> workTimeMap
            , Map<String, Long> workMinute, boolean frontOrBack) {
        if (CollectionUtil.isEmpty(details))
            throw new ServiceException("E01:无数据！");
        // 计划时间分配
        Map<Integer, List<Long>> dataMap = new HashMap<>();

        List<Integer> max = sortJSON(dataMap, details, frontOrBack);
        Map<Long, BomDetailVo> bomMap = details.stream().collect(Collectors.toMap(BomDetailVo::getDetailId, a -> a));

        LocalDateTime endStartTime = null;
        LocalDateTime limitTime = time.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

        Map<String, LocalDateTime> noTime = new HashMap<>(16);
        for (int level : max) {
            List<Long> temp = dataMap.get(level);
            if (CollectionUtil.isNotEmpty(temp)) {
                for (Long key : temp) {
                    BomDetailVo bomDetail = bomMap.get(key);
                    String noKey = bomDetail.getDetailCode();
                    String[] nos = StringUtils.isEmpty(noKey) ? null : noKey.split("\\.");
                    nos = nos == null || nos.length == 1 ? new String[]{"0"} : Arrays.copyOfRange(nos, 0, nos.length - 1);
                    String no = StringUtils.join(nos, '.');
                    noKey = noKey == null ? "0" : noKey;
                    // 获取层级时间
                    LocalDateTime planTime = noTime.get(no) == null ? limitTime : noTime.get(no);
                    if (bomDetail.getProcessList() != null) {
                        List<LineProcessVo> processList = bomDetail.getProcessList();
                        LocalDateTime bomStartTime = null, bomEndTime = null;
                        if (!processList.isEmpty()) {
                            Collections.reverse(processList);
                            for (int i = 0; i < processList.size(); i++) {
                                LineProcessVo object = processList.get(i);
                                LineProcessTime lineProcessTime;
                                if (CollectionUtils.isNotEmpty(object.getProcessTimeList())) {
                                    lineProcessTime = object.getProcessTimeList().get(0);
                                } else {
                                    continue;
                                }
                                String workType;
                                if ("设备".equals(lineProcessTime.getTypeName()))
                                    workType = "设备";
                                else
                                    workType = lineProcessTime.getWorkName();
                                if (StringUtils.isBlank(workType))
                                    workType = "其它";

                                LocalDateTime workStartTime = workTimeMap.get(workType + START_STR);
                                LocalDateTime workEndTime = workTimeMap.get(workType + END_STR);
                                planTime = setPlanTime(planTime, workStartTime, workEndTime);
                                // 设置开始时间
                                if (i == 0) {
                                    bomStartTime = planTime;
                                    bomEndTime = planTime;
                                }

                                Long workSystem = workMinute.getOrDefault(workType, 8L);

                                LocalDateTime tempTime = LocalDateTime.of(workStartTime.getYear(), workStartTime.getMonth(), workStartTime.getDayOfMonth(), planTime.getHour(), planTime.getMinute(), planTime.getSecond());

                                long valTime = frontOrBack ? ChronoUnit.MINUTES.between(workStartTime, tempTime) : ChronoUnit.MINUTES.between(tempTime, workEndTime) + 1;
                                int day;
                                if (0 > valTime) {
                                    valTime = 0L;
                                }
                                // 上次开始时间为本次结束时间
                                LocalDateTime startTime, endTime = planTime;
                                // 当前工序所需时间
                                if (object.getWaitTime() == null)
                                    object.setWaitTime(BigDecimal.ZERO);
                                if (object.getWorkTime() == null)
                                    object.setWorkTime(BigDecimal.ZERO);
                                BigDecimal processTime = object.getWaitTime().add((object.getWorkTime()));
                                // 工种分配列表
                                List<LineProcessTime> processTimeList = object.getProcessTimeList();

                                // 获取节拍时间
                                BigDecimal workTime = null;
                                if (processTimeList != null && processTimeList.size() > 0) {
                                    LineProcessTime pTime = processTimeList.get(0);
                                    if (pTime.getWorkTime() != null) {
                                        if (pTime.getWorkNumber() == null) pTime.setWorkNumber(1);
                                        workTime = pTime.getWorkTime().multiply(new BigDecimal(pTime.getWorkNumber()));
                                    }
                                }
//                                for (LineProcessTime pTime : processTimeList) {
//                                    if (ObjectUtils.isNotEmpty(pTime.getWorkTime())) {
//                                        workTime = ObjectUtils.isNull(workTime) ? pTime.getWorkTime().longValue() : Math.max(workTime, pTime.getWorkTime().longValue());
//                                    }
//                                }
                                if (ObjectUtils.isNotNull(workTime)) {
                                    processTime = workTime;
                                }

                                long totalTime = processTime.multiply(bomDetail.getTotalQuantity()).multiply(new BigDecimal(60)).longValue();
                                // 计算时间
                                if (valTime - totalTime > 0) {
                                    valTime = totalTime;
                                } else {
                                    totalTime = totalTime - valTime;
                                    day = (int) (totalTime / workSystem);
                                    planTime = planTime.plusDays(-(day + 1));
                                    planTime = LocalDateTime.of(planTime.getYear(), planTime.getMonth(), planTime.getDayOfMonth(), workEndTime.getHour(), workEndTime.getMinute(), 0);
                                    if (workEndTime.getHour() == 0 && workEndTime.getMinute() == 0 && workEndTime.getSecond() == 0)
                                        planTime = planTime.plusDays(1);
                                    valTime = day == 0 ? totalTime : totalTime - workSystem * day;
                                }
                                planTime = planTime.plusMinutes(-valTime);
                                startTime = planTime;

                                object.setStartTime(startTime);
                                object.setEndTime(endTime);

                                // 设置结束时间
                                if (i == processList.size() - 1) {
                                    bomStartTime = planTime;
                                    CollectionUtil.reverse(processList);
                                }
                            }
                            bomDetail.setProcessList(processList);
                        }
                        bomDetail.setStartTime(bomStartTime != null ? bomStartTime : planTime);
                        bomDetail.setEndTime(bomEndTime != null ? bomEndTime : planTime);

                        endStartTime = planTime;
                        LocalDateTime tempTime = noTime.get(noKey);
                        if (tempTime == null) {
                            tempTime = planTime;
                        } else {
                            if (planTime.isBefore(tempTime)) {
                                tempTime = planTime;
                            }
                        }
                        noTime.put(noKey, tempTime);
                    }
                }
            }
        }

        BomDetailVo jsonObject = details.get(0);
        limitTime = endStartTime;
        jsonObject.setStartTime(limitTime);
        jsonObject.setEndTime(time.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
    }

    @Override
    public Map<String, Map<String, Long>> getSaleCapacity(Map<String, Double> workTypeWorkTime, Integer dataMonths) {
        List<SalesCapacityVo> data = bomDetailMapper.selectSaleCapacity(dataMonths);
        Map<String, LocalDateTime> workTimeMap = new HashMap<>();
        Map<String, Long> workMinute = new HashMap<>();
        workTypeWorkTime.forEach((k, v) -> setFactoryWorkDate(workTimeMap, workMinute, k, v));
        //将分钟数分到每天上:每个工种
        Map<String, Map<String, Long>> dayMinutes = new HashMap<>();
        DateTimeFormatter keyFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        data.forEach(d -> {
            this.calProcessTime(d.getBomDetails(), d.getShipmentDate(), workTimeMap, workMinute, true);
            //遍历bomDetail
            d.getBomDetails().forEach(b -> {
                //遍历工序，将每个工序的时间，按照工作时间分到每天上
                b.getProcessList().forEach(p -> {
                    if (CollectionUtils.isNotEmpty(p.getProcessTimeList())) {
                        LineProcessTime lineProcessTime = p.getProcessTimeList().get(0);
                        String workType = "设备".equals(lineProcessTime.getTypeName()) ? "设备" : lineProcessTime.getWorkName();
                        workType = workType == null ? "其它" : workType;
                        String workTypeName = lineProcessTime.getWorkName() == null ? "其它" : lineProcessTime.getWorkName();
                        LocalDateTime st = p.getStartTime();
                        LocalDateTime et = p.getEndTime();
//                        Duration duration = Duration.between(st.toLocalDate(), et.toLocalDate());
                        long day = st.toLocalDate().until(et.toLocalDate(), ChronoUnit.DAYS);
                        long dayWorkMinutes = 0L;
                        if (day == 0) {
                            dayWorkMinutes = Duration.between(st, et).toMinutes();
                            String key = st.format(keyFormat);
                            Map<String, Long> workTypeTime = dayMinutes.computeIfAbsent(key, k -> new HashMap<>());
                            long minutes = workTypeTime.getOrDefault(workTypeName, 0L) + dayWorkMinutes;
                            workTypeTime.put(workTypeName, minutes);
                        } else {
                            for (int i = 0; i <= day; i++) {
                                long totalTime = workMinute.get(workType);
                                String key;
                                if (i == 0) {
                                    LocalDateTime t = LocalDateTime.from(st).withHour(0)
                                            .withMinute(0)
                                            .withSecond(0)
                                            .plusMinutes(totalTime);
                                    dayWorkMinutes = Duration.between(st, t).toMinutes();
                                    key = st.format(keyFormat);
                                } else if (i == day) {
                                    LocalDateTime t = LocalDateTime.from(et).withHour(0).withMinute(0).withSecond(0);
                                    dayWorkMinutes = Duration.between(t, et).toMinutes();
                                    key = et.format(keyFormat);
                                } else {
                                    dayWorkMinutes = totalTime;
                                    key = st.plusDays(i).format(keyFormat);
                                }
                                Map<String, Long> workTypeTime = dayMinutes.computeIfAbsent(key, k -> new HashMap<>());
                                long minutes = workTypeTime.getOrDefault(workTypeName, 0L) + dayWorkMinutes;
                                workTypeTime.put(workTypeName, minutes);
                            }
                        }

                    }
                });
            });
        });
        return dayMinutes;
    }

    @Override
    public void recoveryBom(Long itemId) {
        bomDetailMapper.recoveryBom(itemId);
    }

    @Override
    public List<BomDetailVo> getHisLineByBomId(Long bomId) {
        return bomDetailMapper.getHisLineByBomId(bomId);
    }

    @Override
    public BomDetailVo getBomDetailLineProcess(Long bomDetailId) {
        return bomDetailMapper.selectBomDetailLineProcess(bomDetailId);
    }

    @Override
    public BomDetailVo getBomDetailLineProcessType(Long bomId) {
        return bomDetailMapper.selectBomDetailLineProcessType(bomId);
    }

    private static final String START_STR = ":start";
    private static final String END_STR = ":end";


    private List<Integer> sortJSON(Map<Integer, List<Long>> mapBom, List<BomDetailVo> bomList, boolean sort) {
        Set<Integer> set = new HashSet<>(8);
        AtomicInteger max = new AtomicInteger();
        bomList.forEach(a -> {
            int level = a.getLevel();
            max.set(Math.max(level, max.get()));
            List<Long> arrays = mapBom.computeIfAbsent(level, l -> new ArrayList<>());
            arrays.add(a.getDetailId());
            set.add(level);
        });
        List<Integer> list = new ArrayList<>(set);
        if (sort) {
            Collections.sort(list);
        } else {
            list.sort(Collections.reverseOrder());
        }
        return list;
    }

    public LocalDateTime setPlanTime(LocalDateTime planTime, LocalDateTime workStartTime, LocalDateTime workEndTime) {
        LocalDateTime temp = planTime.withYear(workStartTime.getYear())
                .withMonth(workStartTime.getMonthValue())
                .withDayOfMonth(workStartTime.getDayOfMonth());
        long day = Duration.between(workStartTime, workEndTime).toDays();
        if (temp.isAfter(workStartTime) && temp.isBefore(workEndTime)) {
            return planTime;
        } else if (temp.compareTo(workEndTime) >= 0) {
            return planTime.withHour(workEndTime.getHour())
                    .withMinute(workEndTime.getMinute())
                    .withSecond(workEndTime.getSecond());
        } else if (temp.compareTo(workStartTime) <= 0) {
            return planTime.withHour(workStartTime.getHour())
                    .withMinute(workStartTime.getMinute())
                    .withSecond(workStartTime.getSecond());
        }
        throw new UnsupportedOperationException();
    }

    private void setFactoryWorkDate(Map<String, LocalDateTime> factoryWorkDate, Map<String, Long> factoryWorkTime,
                                    String workType, Double hour) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime workStartTime = LocalDateTime.of(now.getYear(), now.getMonth(), now.getDayOfMonth(), 0, 0, 0);
        LocalDateTime workEndTime = workStartTime.plusMinutes((long) (hour * 60));
        factoryWorkDate.put(workType + START_STR, workStartTime);
        factoryWorkDate.put(workType + END_STR, workEndTime);
        factoryWorkTime.put(workType, (long) (hour * 60));
    }
}
